﻿using System;
using System.Collections.Generic;
using Alone.GameObjects;
using Alone.GameObjects.Objects;
using Alone.Particlesystem;
using Alone.Screens;
using AloneLibrary;
using AloneLibrary.GameObjects;
using AloneLibrary.Particlesystem;
using AloneLibrary.Services.Camera;
using AloneLibrary.Services.Input;
using AloneLibrary.Services.Sound;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Net;
using XNAnimation;
using XNAnimation.Controllers;

namespace Alone.Handlers
{
	public interface IPlayerHandler
	{
		LocalPlayer Player { get; }
		List<BasePlayer> Players { get; set; }
		Handgun Weapon { get; }
		RemotePlayer RemotePlayer { get; }
		bool AnyPlayerDied { get; }
		bool IsFired { get; }
        int Score { get; set; }
	}

	public class PlayerHandler : DrawableGameComponent, IPlayerHandler
	{
		private ScreenManager screenManager;
		private NetworkSession networkSession;

		private ICameraHandler camera;
		private IInputHandler input;
		private ISoundManager soundManager;

		private IBallisticsHandler ballisticsHandler;
		private IEnvironmentHandler environmentHandler;
		
		private LocalPlayer localPlayer;
		private List<BasePlayer> players = new List<BasePlayer>();
		private HUD hud;
		private Handgun handgun;
		private List<StaticGameObject> staticObjects;

		private RemotePlayer remotePlayer;
		private ParticleSystem rocketBootsParticleSystem, fireParticleSystem;
		
		private float prevRotation = 0.0f;
		private int elapsedTime = 0;
		private int elapsedTimeHealth = 0;
		private int jetpackInterval = 200;
		private int healthIncreaseInterval = 300;
		private int timeSinceLastUpdate = 0;
		private int updateInterval = 30;
        private int score;

		private bool isFired = false;
		private bool anyPlayerDied = false;

        public int Score
        {
            get { return score; }
            set { score = value; }
        }

		public bool IsFired
		{
			get { return isFired; }
		}

		public Handgun Weapon
		{
			get { return this.handgun; }
		}

		public RemotePlayer RemotePlayer
		{
			get { return remotePlayer; }
			set { remotePlayer = value; }
		}

		public bool AnyPlayerDied
		{
			get { return anyPlayerDied; }
		}


		/// <summary>
		/// Gets or sets the players.
		/// </summary>
		/// <value>The players.</value>
		public List<BasePlayer> Players
		{
			get { return players; }
			set { players = value; }
		}

		/// <summary>
		/// Gets the player.
		/// </summary>
		/// <value>The player.</value>
		public LocalPlayer Player
		{
			get { return localPlayer; }
		}


		/// <summary>
		/// Initializes a new instance of the <see cref="PlayerHandler"/> class.
		/// </summary>
		/// <param name="game">The game.</param>
		/// <param name="networkSession">The network session.</param>
		/// <param name="screenManager">The screen manager.</param>
		public PlayerHandler(Game game, NetworkSession networkSession, ScreenManager screenManager)
			: base(game)
		{
			this.screenManager = screenManager;
			this.networkSession = networkSession;
			this.hud = new HUD(game.Content, screenManager.SpriteBatch);
			Game.Services.AddService(typeof(IPlayerHandler), this);
		}

		/// <summary>
		/// Initializes the component. Override this method to load any non-graphics resources and query for any required services.
		/// </summary>
		public override void Initialize()
		{
			camera = (ICameraHandler)Game.Services.GetService(typeof(ICameraHandler));
			input = (IInputHandler)Game.Services.GetService(typeof(IInputHandler));
			ballisticsHandler = (IBallisticsHandler)Game.Services.GetService(typeof(IBallisticsHandler));
			soundManager = (ISoundManager)Game.Services.GetService(typeof(ISoundManager));
			environmentHandler = (IEnvironmentHandler)Game.Services.GetService(typeof(IEnvironmentHandler));
			
			rocketBootsParticleSystem = new RocketBootsSmokeParticleSystem(Game, Game.Content);
			fireParticleSystem = new RocketBootsFireParticleSystem(Game, Game.Content);

			Game.Components.Add(rocketBootsParticleSystem);
			Game.Components.Add(fireParticleSystem);

			staticObjects = environmentHandler.GetObjectList;
            score = 0;

			base.Initialize();
		}

		/// <summary>
		/// Called when graphics resources need to be loaded. Override this method to load any component-specific graphics resources.
		/// </summary>
		protected override void LoadContent()
		{
			if (networkSession == null || networkSession.IsHost)
			{
				localPlayer = new LocalPlayer(
					Game.Content.Load<SkinnedModel>(@"Models\player\PlayerMarine"),
					new AnimationController(Game.Content.Load<SkinnedModel>(@"Models\player\PlayerMarine").SkeletonBones),
					Game.Content.Load<Effect>(@"Effects\AloneEffect"), // Effect
					new Vector3(2888f, -467f, -3010f), // Position
					new Vector2(0, 0), // Rotation
					1f, // Scale
					Vector3.Zero  // Velocity
				);
			}
			else
			{
				localPlayer = new LocalPlayer(
					Game.Content.Load<SkinnedModel>(@"Models\player\PlayerMarine"),
					new AnimationController(Game.Content.Load<SkinnedModel>(@"Models\player\PlayerMarine").SkeletonBones),
					Game.Content.Load<Effect>(@"Effects\AloneEffect"),
                    new Vector3(2888f, -467f, -2900f),
					new Vector2(0, 0),
					1f,
					Vector3.Zero
				);
			}

			handgun = new Handgun(
				Game,
				Game.Content.Load<SkinnedModel>(@"Models\player\WeaponMachineGun"),
				localPlayer.AnimationController,
				Game.Content.Load<Effect>(@"Effects\AloneEffect"),
				localPlayer.Position,
				Vector2.Zero,
				2f,
				Vector3.Zero
			);

			localPlayer.AnimationController.PlayClip(localPlayer.SkinnedModel.AnimationClips["Idle"]);
			localPlayer.AnimationController.PlaybackMode = PlaybackMode.Forward;
			localPlayer.AnimationController.Speed = 2.0f;
			localPlayer.CurrentStance = localPlayer.NextStance = PlayerStance.Standing;

			if (networkSession != null)
			{
				createRemotePlayer(1);
			}

			this.hud.addTextureSprite("Health", "Health", 40, screenManager.GraphicsDevice.Viewport.Height - 60, 0.0f, 0.1f);
			this.hud.addTextureSprite("Jetpack", "jetpack", 40, screenManager.GraphicsDevice.Viewport.Height - 30, 0.0f, 0.07f);
			this.hud.addFontSprite("Health", 70, screenManager.GraphicsDevice.Viewport.Height - 75, "100", 0.0f, 0.8f);
			this.hud.addFontSprite("Jetpack", 70, screenManager.GraphicsDevice.Viewport.Height - 45, "100", 0.0f, 0.8f);
            
            //this.hud.addTextureSprite("Score", "Score", screenManager.GraphicsDevice.Viewport.Width-40, 40, 0.0f, 0.07f);
            this.hud.addFontSprite("Score", screenManager.GraphicsDevice.Viewport.Width - 140, 20, "0", 0.0f, 0.8f);

			players.Add(localPlayer);
		}

		public void createRemotePlayer(int index)
		{
			remotePlayer = new RemotePlayer(
				Game.Content.Load<SkinnedModel>(@"Models\player\PlayerMarine"),
				new AnimationController(Game.Content.Load<SkinnedModel>(@"Models\player\PlayerMarine").SkeletonBones),
				Game.Content.Load<Effect>(@"Effects\AloneEffect"),
				new Vector3(2330, -618, -1619),
				new Vector2(0, 0),
				1f,
				Vector3.Zero
			);

			players.Add(remotePlayer);
		}

		/// <summary>
		/// Called when the GameComponent needs to be updated. Override this method with component-specific update code.
		/// </summary>
		/// <param name="gameTime">Time elapsed since the last call to Update</param>
		public override void Update(GameTime gameTime)
		{
			timeSinceLastUpdate += gameTime.ElapsedGameTime.Milliseconds;
			
			if (timeSinceLastUpdate >= updateInterval)
			{
				float playerPositionYonTerrain = this.environmentHandler.HeightMap.GetHeight(localPlayer.Position);

				elapsedTime += gameTime.ElapsedGameTime.Milliseconds;
				elapsedTimeHealth += gameTime.ElapsedGameTime.Milliseconds;

				handleInput(gameTime);

				Physics.ApplyPhysics(gameTime, localPlayer, playerPositionYonTerrain);

				localPlayer.JumpPositionY = playerPositionYonTerrain;
				localPlayer.CurrentStance = PlayerStance.Standing;

				if (this.prevRotation != camera.Rotation.X)
				{
					this.hud.updateSpriteRotation("Texture", "Health", "Health", this.prevRotation);
					this.hud.updateSpriteRotation("Texture", "Jetpack", "jetpack", this.prevRotation);
					this.hud.updateSpriteRotation("Font", "Health", "Health", this.prevRotation);
					this.hud.updateSpriteRotation("Font", "Jetpack", "jetpack", this.prevRotation);
                    //this.hud.updateSpriteRotation("Texture", "Score", "Score", this.prevRotation);
                    this.hud.updateSpriteRotation("Score", "Jetpack", "jetpack", this.prevRotation);
					this.prevRotation = camera.Rotation.X;
				}

				if (this.Player.Position.Y == playerPositionYonTerrain && elapsedTime > jetpackInterval)
				{
					if (this.Player.Jetpack != 50)
					{
						this.Player.Jetpack++;
					}
					elapsedTime = 0;
				}

				if (elapsedTimeHealth > healthIncreaseInterval)
				{
					if (this.Player.Hp != 100)
						this.Player.incHealth(1);
					elapsedTimeHealth = 0;
				}

                this.hud.updateFontSprite("Health", "Health", localPlayer.Hp.ToString());
                this.hud.updateFontSprite("Jetpack", "jetpack", localPlayer.Jetpack.ToString());
                this.hud.updateFontSprite("Score", "Score", score.ToString());

				timeSinceLastUpdate -= updateInterval;

				base.Update(gameTime);
			}
		}

		/// <summary>
		/// Handles the input.
		/// </summary>
		private void handleInput(GameTime gameTime)
		{
			if (input.Back.Status && input.Back.Updated)
			{
				this.Enabled = false;
				screenManager.AddScreen(new PauseMenuScreen(networkSession), PlayerIndex.One);
			}

			// Clamp Rotation around X to +/- 45 degrees
			localPlayer.Rotation.X = MathHelper.Clamp(localPlayer.Rotation.X - input.Rotation.X + MathHelper.PiOver4, 0, MathHelper.PiOver2) - MathHelper.PiOver4;
			localPlayer.Rotation.Y -= input.Rotation.Y;

			Matrix rotationMatrix = Matrix.CreateRotationY(localPlayer.Rotation.Y);
			Vector3 movementVelocity = Vector3.Zero;

			if (input.Aim.Status)
			{
				localPlayer.CurrentStance = PlayerStance.Aiming;
				camera.Zoomed = true;

				isFired = (input.Fire.Status && handgun.Ammo > 0);
				if (isFired)
				{
                    score -= 10;
					ballisticsHandler.UpdateShot(gameTime, handgun);
					localPlayer.CurrentStance = PlayerStance.Shooting;
				}
			}
			else
			{
				camera.Zoomed = false;

				// Movement
				if (input.Movement != Vector2.Zero)
				{
					if (input.Movement.Y != 0)
					{
						if (input.Movement.Y > 0 && !localPlayer.DisabledForward)
						{
							localPlayer.CurrentStance = PlayerStance.WalkingForward;
							movementVelocity.Z += -localPlayer.ForwardSpeed;
						}
						else if (!localPlayer.DisabledBackward)
						{
							localPlayer.CurrentStance = PlayerStance.WalkingBackward;
							movementVelocity.Z += localPlayer.ForwardSpeed;
						}
					}

					if (input.Movement.X != 0)
					{
						localPlayer.CurrentStance = (input.Movement.Y >= 0 ? PlayerStance.WalkingForward : PlayerStance.WalkingBackward);

						if (input.Movement.X > 0 && !localPlayer.DisabledRight)
						{
							movementVelocity.X += localPlayer.ForwardSpeed;
						}
						else if (!localPlayer.DisabledLeft)
						{
							movementVelocity.X -= localPlayer.ForwardSpeed;
						}
					}
				}

				if (input.Jump.Status && input.Jump.Updated && localPlayer.jump())
				{
					movementVelocity.Y += localPlayer.JumpForce;
				}

				if (input.Boost.Status && localPlayer.useJetpack())
				{
					movementVelocity.Y += localPlayer.JetpackForce * (gameTime.ElapsedGameTime.Milliseconds / 1000f);

					Vector3 rocketBootsPos = new Vector3(localPlayer.Position.X, localPlayer.Position.Y, localPlayer.Position.Z);
					rocketBootsParticleSystem.AddParticle(rocketBootsPos, Vector3.Zero);
					fireParticleSystem.AddParticle(rocketBootsPos, Vector3.Zero);
				}
			}

			movementVelocity = Vector3.Transform(movementVelocity, rotationMatrix);

			localPlayer.Position.X = MathHelper.Clamp(localPlayer.Position.X + movementVelocity.X, -3400, 3400);
			localPlayer.Position.Z = MathHelper.Clamp(localPlayer.Position.Z + movementVelocity.Z, -3400, 3400);
			localPlayer.Velocity.Y += movementVelocity.Y;

			foreach (BasePlayer p in players)
			{
				if (p.Hp == 0)
				{
					anyPlayerDied = true;
				}
			}

			if (localPlayer.CurrentStance != localPlayer.NextStance)
			{
				switch (localPlayer.CurrentStance)
				{
					case PlayerStance.Standing:
						localPlayer.AnimationController.LoopEnabled = true;
						localPlayer.AnimationController.PlaybackMode = PlaybackMode.Forward;
						localPlayer.AnimationController.CrossFade(localPlayer.SkinnedModel.AnimationClips["Idle"], TimeSpan.FromSeconds(0.2));
						break;
					case PlayerStance.WalkingForward:
						localPlayer.AnimationController.LoopEnabled = true;
						localPlayer.AnimationController.PlaybackMode = PlaybackMode.Forward;
						localPlayer.AnimationController.CrossFade(localPlayer.SkinnedModel.AnimationClips["Run"], TimeSpan.FromSeconds(0.2));
						break;
					case PlayerStance.WalkingBackward:
						localPlayer.AnimationController.LoopEnabled = true;
						localPlayer.AnimationController.PlaybackMode = PlaybackMode.Backward;
						localPlayer.AnimationController.CrossFade(localPlayer.SkinnedModel.AnimationClips["Run"], TimeSpan.FromSeconds(0.2));
						break;
					case PlayerStance.Aiming:
						localPlayer.AnimationController.LoopEnabled = false;
						localPlayer.AnimationController.PlaybackMode = PlaybackMode.Forward;
						localPlayer.AnimationController.CrossFade(localPlayer.SkinnedModel.AnimationClips["Aim"], TimeSpan.FromSeconds(0.2));
						break;
				}
			}
			
			localPlayer.NextStance = localPlayer.CurrentStance;

			if (Player.Move && !soundManager.IsPlaying("gravelwalking"))
			{
				soundManager.Play("gravelwalking");
			}

			rocketBootsParticleSystem.SetCamera(camera.View, camera.Projection);
			fireParticleSystem.SetCamera(camera.View, camera.Projection);
			
			foreach (BasePlayer bp in players)
			{
				bp.AnimationController.Update(gameTime.ElapsedGameTime, Matrix.Identity);
			}
			
			localPlayer.viewFrustum.Matrix = camera.View * camera.Projection;
		}

		/// <summary>
		/// Called when the DrawableGameComponent needs to be drawn. Override this method with component-specific drawing code. Reference page contains links to related conceptual articles.
		/// </summary>
		/// <param name="gameTime">Time passed since the last call to Draw.</param>
		public override void Draw(GameTime gameTime)
		{
			if (isFired)//&& weapon.Ammo > 0)
			{
				ballisticsHandler.drawRay();
				soundManager.Play("explosion");
			}

			foreach (BasePlayer p in players)
			{
				p.Draw(gameTime, camera);
			}

			handgun.Draw(gameTime, camera);
			hud.drawSprites();
			
			base.Draw(gameTime);
		}
	}
}
